package org.xtext.runtime.plan;

import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.xtext.runtime.term.Variable;


public class PlanStack {
	private List<IPlanStep> stack;
	private IPlanStep current;
	private int state;
	private Map<Integer, IPlanStep> recoveryPlans = new HashMap<Integer, IPlanStep>();
	private long start;
	private String label;

	public static final int STATE_ACTIVE						 = 0;
	public static final int STATE_SUSPENDED						 = 1;
	public static final int STATE_FAILED						 = 2;
	public static final int STATE_SUCCEEDED						 = 3;

	
	public PlanStack(String label) {
		stack = new LinkedList<IPlanStep>();
		start = System.currentTimeMillis();
		this.label = label;
	}

	public PlanStack(String label, IPlanStep plan) {
		this(label);
		addPlanStep(plan);
	}

	public long duration() {
		return System.currentTimeMillis()-start;
	}
	
	public String label() {
		return label;
	}

	public String toString() {
		String output = this.label + ":\n";
		for (int i=stack.size()-1; i>=0; i--) {
			if ( !(stack.get(i) instanceof BlockStep) ) {
				output += "\t"+stack.get(i).toString() +"\n";
			}
		}
		return output;
	}
	
	public String stateString() {
		switch (state) {
		case STATE_ACTIVE:
			return "ACTIVE";
		case STATE_SUSPENDED:
			return "SUSPENDED";
		case STATE_FAILED:
			return "FAILED";
		case STATE_SUCCEEDED:
			return "SUCCEEDED";
		}
		return "NO STATE";
	}
	
	public List<IPlanStep> getStack() {
		return stack;
	}
	
	public IPlanStep getCurrent() {
		return current;
	}
	
	public void addPlanStep(IPlanStep step) {
		stack.add(0, step);
	}
	
	public void back() {
		stack.add(0, current);
	}
	
	public IPlanStep getNextActivity() {
		if (stack.isEmpty()) {
			state = PlanStack.STATE_SUCCEEDED;			
			current = null;
		} else {
			current = stack.remove(0);
		}
		
		return current;
	}
	
	public void suspend() {
		state = PlanStack.STATE_SUSPENDED;
	}
	
	public void resume() {
		state = PlanStack.STATE_ACTIVE;
	}
	
	public void fail() {
		state = PlanStack.STATE_FAILED;
	}
	
	public boolean isSuspended() {
		return state == PlanStack.STATE_SUSPENDED;
	}
	
	public boolean isActive() {
		return state == PlanStack.STATE_ACTIVE;
	}

	public boolean isFailed() {
		return state == PlanStack.STATE_FAILED;
	}

	public boolean isSucceeded() {
		return state == PlanStack.STATE_SUCCEEDED;
	}

	/**
	 * Adds a recovery plan to the commitment. This plan is linked to the stack size when it
	 * is added. The plan is triggered in the case where the commitment has failed and the
	 * stack is being rolled back. When the stack level corresponds to a recovery plan, recovery
	 * occurs.
	 * 
	 * @param plan
	 */
	public void addRecoveryPlan(IPlanStep plan) {
		recoveryPlans.put(stack.size(), plan);
	}
	
	public boolean rollback() {
		while (!stack.isEmpty() && (recoveryPlans.get(stack.size()) == null)) {
			stack.remove(0);
		}
		
		IPlanStep plan = recoveryPlans.remove(stack.size());
		if (plan == null) return false;

		addPlanStep(plan);
		
		this.resume();
		
		return true;
	}

	public void printTrace() {
		System.out.println(toString());
	}
}
